import os

import requests
import m3u8
import re
from urllib.parse import urljoin
import concurrent.futures
from datetime import datetime
import json
import subprocess
import shutil
import time


class M3UParser:
    @staticmethod
    def parse(file_path_or_url):
        """
        解析M3U文件，提取所有流媒体URL
        支持本地文件或网络URL
        返回格式: [{'url': 'http://...', 'metadata': {...}}, ...]
        """
        try:
            # 读取M3U内容
            if file_path_or_url.startswith(('http://', 'https://')):
                content = requests.get(file_path_or_url).text
            else:
                with open(file_path_or_url, 'r') as f:
                    content = f.read()

            streams = []
            base_url = file_path_or_url.rsplit('/', 1)[0] + '/' if '/' in file_path_or_url else ''

            # 分组解析：兼容EXTINF和直接URL两种情况
            current_meta = {}
            for line in content.splitlines():
                line = line.strip()
                if not line or line.startswith('#EXTM3U'):
                    continue

                # 解析元数据
                if line.startswith('#EXTINF'):
                    current_meta = {
                        'duration': float(re.search(r'-?\d+', line).group()),
                        'title': line.split(',', 1)[1] if ',' in line else ''
                    }
                elif not line.startswith('#'):
                    # 处理相对路径
                    url = urljoin(base_url, line) if not line.startswith(
                        ('http://', 'https://', 'rtmp://', 'udp://')) else line

                    # 如果是M3U8嵌套，继续解析
                    if any(x in line.lower() for x in ['.m3u8', '.m3u']):
                        try:
                            nested_streams = M3UParser.parse(url)
                            streams.extend(nested_streams)
                        except:
                            streams.append({'url': url, 'metadata': current_meta.copy()})
                    else:
                        streams.append({'url': url, 'metadata': current_meta.copy()})
                    current_meta = {}

            return streams

        except Exception as e:
            raise Exception(f"M3U解析失败: {str(e)}")


def get_ffprobe_path():
    """获取 ffprobe 可执行文件路径"""
    # 方法1：检查环境变量PATH
    ffprobe = shutil.which('ffprobe')
    if ffprobe:
        return ffprobe

    # 方法2：检查常见安装路径
    common_paths = [
        # Windows
        'C:\\ffmpeg\\bin\\ffprobe.exe',
        'C:\\Program Files\\ffmpeg\\bin\\ffprobe.exe',
        # Linux/Mac
        '/usr/local/bin/ffprobe',
        '/usr/bin/ffprobe',
        '/opt/homebrew/bin/ffprobe'  # M1 Mac
    ]

    for path in common_paths:
        if os.path.exists(path):
            return path

    # 方法3：提示用户手动指定
    raise RuntimeError(
        "未找到 ffprobe 可执行文件\n"
        "请执行以下操作之一：\n"
        "1. 安装 FFmpeg 并添加到系统PATH\n"
        "2. 手动指定 ffprobe 路径\n"
        "各系统安装方法：\n"
        "Windows: choco install ffmpeg\n"
        "Mac: brew install ffmpeg\n"
        "Linux: sudo apt install ffmpeg"
    )


class IPTVChecker:
    def __init__(self, timeout=5, max_workers=10):
        self.timeout = timeout
        self.max_workers = max_workers
        self.session = requests.Session()
        self.session.headers.update({'User-Agent': 'IPTV-Checker/2.0'})
        self.ffprobe_path = get_ffprobe_path()

    def _check_hls(self, url):
        """专用HLS检测方法"""
        try:
            # 快速检查M3U8可访问性
            start_time = time.time()
            response = self.session.get(url, timeout=self.timeout)
            response.raise_for_status()

            # 验证M3U8基础格式
            if '#EXTM3U' not in response.text[:10]:
                return {'error': 'Invalid M3U8 header'}

            # 解析主播放列表
            playlist = m3u8.loads(response.text, uri=url)

            # 如果是多码率流，选择第一个变体测试
            if playlist.is_variant and playlist.playlists:
                test_url = urljoin(url, playlist.playlists[0].uri)
                return self._check_hls(test_url)

            # 检查关键分片（前3个）
            segments = [urljoin(url, seg.uri) for seg in playlist.segments[:3]]
            success = 0
            for seg_url in segments:
                try:
                    resp = self.session.head(seg_url, timeout=self.timeout)
                    if resp.status_code == 200:
                        success += 1
                except:
                    pass

            return {
                'protocol': 'HLS',
                'online': success > 0,
                'response_time': round((time.time() - start_time) * 1000),
                'segment_test': f"{success}/{len(segments)}",
                'is_live': not playlist.is_variant,
                'has_encryption': bool(playlist.keys and playlist.keys[0])
            }

        except Exception as e:
            return {
                'protocol': 'HLS',
                'online': False,
                'error': str(e)
            }

    def _check_generic(self, url):
        """通用流媒体检测"""
        result = {
            'url': url,
            'protocol': '',
            'online': False,
            'response_time': 0,
            'error': ''
        }

        start_time = time.time()

        try:
            # 协议识别
            if 'm3u8' in url.lower():
                return {**self._check_hls(url), 'url': url}
            elif 'rtmp://' in url.lower():
                protocol = 'RTMP'
                cmd = [self.ffprobe_path, '-v', 'error', '-show_entries', 'format=duration', '-of', 'json', url]
            else:
                protocol = 'UDP/HTTP' if 'udp://' in url.lower() else 'HTTP-FLV'
                cmd = [self.ffprobe_path, '-v', 'error', '-show_entries', 'stream=codec_name', '-of', 'json', url]

            # 执行检测
            output = subprocess.run(cmd, capture_output=True, text=True, timeout=self.timeout)

            if output.returncode == 0:
                result.update({
                    'protocol': protocol,
                    'online': True,
                    'response_time': round((time.time() - start_time) * 1000)
                })
            else:
                result['error'] = output.stderr

        except subprocess.TimeoutExpired:
            result['error'] = f"Timeout after {self.timeout} seconds"
        except Exception as e:
            result['error'] = str(e)
        finally:
            if not result.get('response_time'):
                result['response_time'] = round((time.time() - start_time) * 1000)

        return result

    def check_stream(self, stream_info):
        """检测单个流（自动选择检测方法）"""
        url = stream_info['url']

        # 先尝试HEAD请求快速检查
        try:
            if url.startswith(('http://', 'https://')):
                resp = self.session.head(url, timeout=3)
                if resp.status_code == 404:
                    return {
                        'url': url,
                        'online': False,
                        'error': '404 Not Found',
                        'protocol': 'HTTP'
                    }
        except:
            pass

        # 深度检测
        return self._check_generic(url)

    def batch_check(self, stream_list):
        """批量检测流媒体"""
        results = []
        with concurrent.futures.ThreadPoolExecutor(max_workers=self.max_workers) as executor:
            future_to_url = {executor.submit(self.check_stream, stream): stream for stream in stream_list}
            for future in concurrent.futures.as_completed(future_to_url):
                results.append(future.result())
        return results


def generate_report(streams, results, output_file='iptv_report.html'):
    """生成增强版HTML报告"""
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    online_count = sum(1 for r in results if r['online'])

    # 合并元数据
    for result in results:
        stream = next((s for s in streams if s['url'] == result['url']), None)
        if stream and 'metadata' in stream:
            result['metadata'] = stream['metadata']

    html = f"""
    <html>
    <head>
        <title>IPTV源检测报告 - {timestamp}</title>
        <style>
            body {{ font-family: Arial, sans-serif; margin: 20px; }}
            h1 {{ color: #333; }}
            .summary {{ background: #f8f9fa; padding: 15px; border-radius: 5px; }}
            table {{ border-collapse: collapse; width: 100%; }}
            th, td {{ border: 1px solid #ddd; padding: 8px; }}
            th {{ background-color: #f2f2f2; position: sticky; top: 0; }}
            .online {{ background-color: #d4edda; }}
            .offline {{ background-color: #f8d7da; }}
            .monospace {{ font-family: monospace; }}
            .tab-content {{ display: none; }}
            .tab-content.active {{ display: block; }}
            .tab-button {{
                padding: 8px 15px;
                margin-right: 5px;
                background: #e9ecef;
                border: none;
                border-radius: 4px;
                cursor: pointer;
            }}
            .tab-button.active {{ background: #007bff; color: white; }}
        </style>
        <script>
            function openTab(evt, tabName) {{
                var tabcontent = document.getElementsByClassName("tab-content");
                for (var i = 0; i < tabcontent.length; i++) {{
                    tabcontent[i].classList.remove("active");
                }}
                var tabbuttons = document.getElementsByClassName("tab-button");
                for (var i = 0; i < tabbuttons.length; i++) {{
                    tabbuttons[i].classList.remove("active");
                }}
                document.getElementById(tabName).classList.add("active");
                evt.currentTarget.classList.add("active");
            }}
        </script>
    </head>
    <body>
        <h1>IPTV源检测报告</h1>
        <div class="summary">
            <p><b>生成时间:</b> {timestamp}</p>
            <p><b>检测总数:</b> {len(results)} | 
               <b>可用:</b> <span style="color: green;">{online_count}</span> | 
               <b>不可用:</b> <span style="color: red;">{len(results) - online_count}</span></p>
        </div>

        <div>
            <button class="tab-button active" onclick="openTab(event, 'all')">全部流</button>
            <button class="tab-button" onclick="openTab(event, 'online')">可用流</button>
            <button class="tab-button" onclick="openTab(event, 'offline')">不可用流</button>
        </div>

        <div id="all" class="tab-content active">
            <h2>全部流媒体源</h2>
            <table>
                <tr>
                    <th>状态</th>
                    <th>频道名称</th>
                    <th>URL</th>
                    <th>协议</th>
                    <th>响应时间</th>
                    <th>详细信息</th>
                </tr>
    """

    # 全部流
    for r in sorted(results, key=lambda x: (not x['online'], x.get('protocol', ''))):
        status = '✅ 在线' if r['online'] else '❌ 离线'
        row_class = 'online' if r['online'] else 'offline'
        title = r.get('metadata', {}).get('title', 'N/A')

        details = []
        if r['online']:
            if r['protocol'] == 'HLS':
                details.append(f"分片测试: {r.get('segment_test', 'N/A')}")
                if r.get('is_live'):
                    details.append("类型: 直播流")
                if r.get('has_encryption'):
                    details.append("加密: 是")
            details.append(f"响应: {r['response_time']}ms")
        else:
            details.append(r.get('error', 'Unknown error'))

        html += f"""
            <tr class="{row_class}">
                <td>{status}</td>
                <td>{title}</td>
                <td class="monospace"><a href="{r['url']}" target="_blank">{r['url'][:50]}...</a></td>
                <td>{r.get('protocol', '')}</td>
                <td>{r['response_time']}ms</td>
                <td>{' | '.join(details)}</td>
            </tr>
        """

    html += """
            </table>
        </div>

        <div id="online" class="tab-content">
            <h2>可用流媒体源</h2>
            <table>
                <tr>
                    <th>频道名称</th>
                    <th>URL</th>
                    <th>协议</th>
                    <th>响应时间</th>
                    <th>详细信息</th>
                </tr>
    """

    # 仅在线流
    for r in filter(lambda x: x['online'], results):
        title = r.get('metadata', {}).get('title', 'N/A')
        details = []
        if r['protocol'] == 'HLS':
            details.append(f"分片测试: {r.get('segment_test', 'N/A')}")
            if r.get('is_live'):
                details.append("类型: 直播流")
            if r.get('has_encryption'):
                details.append("加密: 是")
        details.append(f"响应: {r['response_time']}ms")

        html += f"""
            <tr class="online">
                <td>{title}</td>
                <td class="monospace"><a href="{r['url']}" target="_blank">{r['url'][:50]}...</a></td>
                <td>{r.get('protocol', '')}</td>
                <td>{r['response_time']}ms</td>
                <td>{' | '.join(details)}</td>
            </tr>
        """

    html += """
            </table>
        </div>

        <div id="offline" class="tab-content">
            <h2>不可用流媒体源</h2>
            <table>
                <tr>
                    <th>频道名称</th>
                    <th>URL</th>
                    <th>协议</th>
                    <th>错误信息</th>
                </tr>
    """

    # 仅离线流
    for r in filter(lambda x: not x['online'], results):
        title = r.get('metadata', {}).get('title', 'N/A')
        html += f"""
            <tr class="offline">
                <td>{title}</td>
                <td class="monospace"><a href="{r['url']}" target="_blank">{r['url'][:50]}...</a></td>
                <td>{r.get('protocol', '')}</td>
                <td>{r.get('error', 'Unknown error')}</td>
            </tr>
        """

    html += """
            </table>
        </div>
    </body>
    </html>
    """

    with open(output_file, 'w', encoding='utf-8') as f:
        f.write(html)
    print(f"报告已生成: {output_file}")


if __name__ == '__main__':
    # 使用示例
    m3u_file = "https://gitee.com/laiyujun/PythonProject/raw/master/iptv/湖南电信_220.169.109.72_802_IPTV.m3u"  # 可以是本地文件或网络URL

    print(f"正在解析M3U文件: {m3u_file}")
    try:
        streams = M3UParser.parse(m3u_file)
        print(f"找到 {len(streams)} 个流媒体源")

        checker = IPTVChecker(timeout=8, max_workers=15)
        print("开始检测流媒体可用性...")
        results = checker.batch_check(streams)

        generate_report(streams, results)

        # 保存JSON格式结果
        with open('iptv_results.json', 'w') as f:
            json.dump(results, f, indent=2)

    except Exception as e:
        print(f"发生错误: {str(e)}")
